#include "Instance.h"

#include <iostream>

namespace ErkangShuttle
{

	Instance::Instance(InstanceStruct* _pInstanceStruct): m_ListOfInstanceFunctions(*_pInstanceStruct->_pListOfInstanceFunctions), m_VkInstanceCreateInfo(_pInstanceStruct->_VkInstanceCreateInfo), m_VkInstance(_pInstanceStruct->_VkInstance), m_VkInstanceExtensions(_pInstanceStruct->_VkInstanceExtensions), m_VkInstanceExtensionNames(_pInstanceStruct->_VkInstanceExtensionNames)
	{
	}

	Instance::~Instance()
	{
		for (auto& AvailablePhysicalDeviceStructInstance : m_AllAvailablePhysicalDeviceStructInstance)
		{
			delete AvailablePhysicalDeviceStructInstance.second->_pPhysicalDevice;
			delete AvailablePhysicalDeviceStructInstance.second->_PhysicalDeviceStruct._pListOfPhysicalDeviceFunctions;
			delete &AvailablePhysicalDeviceStructInstance;
		}
		m_AllAvailablePhysicalDeviceStructInstance.clear();
		m_ListOfInstanceFunctions.vkDestroyInstance(m_VkInstance, nullptr);
	}

	void Instance::init()
	{
		if (!GetAllAvailablePhysicalDevices())
		{
			throw(std::string("Get All Available Physical Devices Failure."));
		}
	}

	uint32_t Instance::getSupportPhysicalDeviceCount()
	{
		return (uint32_t)m_AllAvailableVkPhysicalDevices.size();
	}

	PhysicalDevice& Instance::getPhysicalDevice(const std::string& _PhysicalDeviceName)
	{
		auto PhysicalDevice_ = m_AllAvailablePhysicalDeviceStructInstance.find(_PhysicalDeviceName);
		if (PhysicalDevice_ != m_AllAvailablePhysicalDeviceStructInstance.end())
		{
			return *PhysicalDevice_->second->_pPhysicalDevice;
		}
		else
		{
			throw(_PhysicalDeviceName + std::string(" Physical Device Can Not Be Found."));
		}
	}

	PhysicalDevice& Instance::operator[](const std::string& _PhysicalDeviceName)
	{
		if (_PhysicalDeviceName == "default")
		{
			return getPhysicalDevice(m_DefaultPhysicalDeviceName);
		}
		return getPhysicalDevice(_PhysicalDeviceName);
	}

	const std::vector<std::string>& Instance::getAllAvailablePhysicalDevices()
	{
		return m_AllAvailablePhysicalDeviceName;
	}

	void Instance::setDefaultPhysicalDevice(const std::string& _PhysicalDeviceName)
	{
		auto PhysicalDevice_ = m_AllAvailablePhysicalDeviceStructInstance.find(_PhysicalDeviceName);
		if (PhysicalDevice_ != m_AllAvailablePhysicalDeviceStructInstance.end())
		{
			m_DefaultPhysicalDeviceName = PhysicalDevice_->first;
		}
		else
		{
			throw(_PhysicalDeviceName + std::string(" Physical Device Can Not Be Found."));
		}
	}

	bool Instance::GetAllAvailablePhysicalDevices()
	{
		uint32_t PhysicalDevicesCount = 0;
		VkResult ECResult = m_ListOfInstanceFunctions.vkEnumeratePhysicalDevices(m_VkInstance, &PhysicalDevicesCount, nullptr);
		m_AllAvailableVkPhysicalDevices.resize(PhysicalDevicesCount);
		VkResult EPResult = m_ListOfInstanceFunctions.vkEnumeratePhysicalDevices(m_VkInstance, &PhysicalDevicesCount, m_AllAvailableVkPhysicalDevices.data());
		if (ECResult != VK_SUCCESS || EPResult != VK_SUCCESS || PhysicalDevicesCount == 0)
		{
			return false;
		}
		for (auto& AvailableVkPhysicalDevice : m_AllAvailableVkPhysicalDevices)
		{
			PhysicalDeviceStructInstance* pPhysicalDeviceStructInstance = new PhysicalDeviceStructInstance;
			pPhysicalDeviceStructInstance->_PhysicalDeviceStruct._pListOfPhysicalDeviceFunctions = LoadPhysicalDeviceFunctions(&AvailableVkPhysicalDevice);
			if (pPhysicalDeviceStructInstance->_PhysicalDeviceStruct._pListOfPhysicalDeviceFunctions == nullptr)
			{
				return false;
			}
			pPhysicalDeviceStructInstance->_PhysicalDeviceStruct._VkPhysicalDevice = AvailableVkPhysicalDevice;
			PhysicalDevice* pPhysicalDevice = new PhysicalDevice(&pPhysicalDeviceStructInstance->_PhysicalDeviceStruct);
			pPhysicalDevice->init();
			pPhysicalDeviceStructInstance->_pPhysicalDevice = pPhysicalDevice;
			m_AllAvailablePhysicalDeviceName.push_back(pPhysicalDeviceStructInstance->_PhysicalDeviceStruct._VkPhysicalDeviceProperties.deviceName);
			m_AllAvailablePhysicalDeviceStructInstance.emplace(pPhysicalDeviceStructInstance->_PhysicalDeviceStruct._VkPhysicalDeviceProperties.deviceName, pPhysicalDeviceStructInstance);
		}
		return true;
	}
	ListOfPhysicalDeviceFunctions* Instance::LoadPhysicalDeviceFunctions(VkPhysicalDevice* _pVkPhysicalDevice)
	{
		ListOfPhysicalDeviceFunctions* pListOfPhysicalDeviceFunctions = new ListOfPhysicalDeviceFunctions;
#define physicaldevicefunction(name)																							\
		pListOfPhysicalDeviceFunctions->##name = (PFN_##name)g_vkGetInstanceProcAddr((VkInstance)*_pVkPhysicalDevice, #name);	\
		if(pListOfPhysicalDeviceFunctions->##name == nullptr)																	\
		{																														\
			return nullptr;																										\
		}
		physicaldevicefunction(vkCreateDevice);
		physicaldevicefunction(vkEnumerateDeviceExtensionProperties);
		physicaldevicefunction(vkEnumerateDeviceLayerProperties);
		physicaldevicefunction(vkGetPhysicalDeviceFeatures);
		physicaldevicefunction(vkGetPhysicalDeviceFormatProperties);
		physicaldevicefunction(vkGetPhysicalDeviceImageFormatProperties);
		physicaldevicefunction(vkGetPhysicalDeviceMemoryProperties);
		physicaldevicefunction(vkGetPhysicalDeviceProperties);
		physicaldevicefunction(vkGetPhysicalDeviceQueueFamilyProperties);
		physicaldevicefunction(vkGetPhysicalDeviceSparseImageFormatProperties);
		return pListOfPhysicalDeviceFunctions;
	}
}
